﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace XTYAPI.通讯
{
  public  class TCPClient
    {
        private Socket tcpClient;
        /// <summary>
        /// 等待时间ms
        /// </summary>
        public int WaitTimes { get;  set; } = 30*100;//3秒钟超时
        byte[] reciveBuffer=new byte[1024*1024*10];
        private bool connected=false;
        public bool Connected { get { return connected; } }
        byte[] lastBuffer =new byte[1024];
        public event Action<byte[]> ReceiveEventArry;
        public Encoding Encoding { get; set; } = Encoding.ASCII;
        Stopwatch Stopwatch = new Stopwatch();
        private TCPClientResult tCPClientResult = new TCPClient.TCPClientResult();
        public bool IsAsync { get; set; }
        public byte[] ReciveBffer { get; private set; } = new byte[1024];
        public bool Connect(string ip,int port,bool IsAsync=false)
        {
            this.IsAsync = IsAsync;
            tcpClient = new Socket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp);
            //tcpClient.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReceiveTimeout, 2000);
            tcpClient.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.SendTimeout, 2000);
            IPEndPoint iPEndPoint = new IPEndPoint(IPAddress.Parse(ip),port);
            IAsyncResult asyncResult=  tcpClient.BeginConnect(iPEndPoint,null,null);
            asyncResult.AsyncWaitHandle.WaitOne(3000);//连续3秒钟，没有连上异常，不能不写时间，否则会等待时间长，以及误判连线OK
            if (!asyncResult.IsCompleted|| !tcpClient.Connected)
            {
                tcpClient.Close();
                connected = false;
                return false;
            }
  
            if (IsAsync)
            {
                tcpClient.BeginReceive(reciveBuffer, 0, reciveBuffer.Length, 0, new AsyncCallback(ReceiveCallBack), null);
            }
            else
            {
                //连接或重连，如果端口有数据，先释放掉，防止后期污染
                int count = tcpClient.Available;
                if (count > 0)
                {
                    byte[] bs = new byte[count];
                    tcpClient.Receive(bs);
                }
            }
            connected = true;
            return true;
        }

        private void ReceiveCallBack(IAsyncResult ar)
        {
            try
            {
                int REnd = tcpClient.EndReceive(ar);
                Array.Copy(reciveBuffer, ReciveBffer,REnd);
                if (!CheckObj(ReciveBffer,lastBuffer))
                {
                    ReceiveEventArry?.Invoke(ReciveBffer);   
                    lastBuffer = reciveBuffer;
                }             
                connected = true;
                tcpClient.BeginReceive(reciveBuffer, 0, reciveBuffer.Length, SocketFlags.None, new AsyncCallback(ReceiveCallBack), null);
            }
            catch (Exception ex)
            {
                connected = false;
            }
        }

        public void DisConnect()
        {
            if (tcpClient != null)
            {
                if (tcpClient.Connected) tcpClient.Close();
            }
        }
        public byte[] Send(byte[] SendByte)
        {
            try
            {
                tcpClient.Send(SendByte, SendByte.Length, SocketFlags.None);
                Thread.Sleep(50);
                int k = tcpClient.Available;
                int timer = 0;
                while (k == 0)
                {
                    timer++;
                    k = tcpClient.Available;
                    Thread.Sleep(10);
                    if (timer > WaitTimes)
                        break;
                }
                byte[] myBufferBytes = new byte[k];
                tcpClient.Receive(myBufferBytes, k, SocketFlags.None);
                return myBufferBytes;
            }
            catch (Exception)
            {
                return null;
            }
            finally
            {
             
            }
        }
        public TCPClientResult Send(string  sendMsg,string endChar="\r")
        {
            try
            {
                Stopwatch.Restart();
                int count = tcpClient.Available;
                if (count > 0)
                {
                    byte[] bs = new byte[count];
                    tcpClient.Receive(bs);//释放掉缓存垃圾数据
                }
                tCPClientResult.SendMsg = sendMsg;
                tCPClientResult.SendBytes = Encoding.GetBytes(sendMsg);
                tcpClient.Send(tCPClientResult.SendBytes, tCPClientResult.SendBytes.Length, SocketFlags.None);
                bool run = true;
                StringBuilder sb = new StringBuilder();
                do
                {
                    Thread.Sleep(5);
                    int available = tcpClient.Available;
                    if (available > 0)
                    {
                        byte[] reciveBytes = new byte[available];
                        int size = tcpClient.Receive(reciveBytes);
                        string data = Encoding.GetString(reciveBytes);
                        sb.Append(data);
                        if (data.Contains(endChar) && sb.ToString().EndsWith(endChar))
                        {
                            run = false;
                            tCPClientResult.Result = true;
                            tCPClientResult.ErrMSG = $@"获取到了完整数据，依据{endChar}";
                            tCPClientResult.ReciveMsg = sb.ToString();
                        }
                    }

                    if (Stopwatch.ElapsedMilliseconds > WaitTimes)
                    {
                        tCPClientResult.Result = false;
                        tCPClientResult.ErrMSG = $"未在{WaitTimes}ms时间内获取到合法数据";
                        break;
                    }
                } while (run);
                tCPClientResult.Time = (uint)Stopwatch.ElapsedMilliseconds;
                Stopwatch.Stop();
                return tCPClientResult;
            }
            catch (SocketException sex)
            {
                tCPClientResult.Result = false;
                tCPClientResult.ErrMSG = sex.Message.ToString();
                Stopwatch.Stop();
                connected = false;
                return tCPClientResult;
            }
            catch (Exception ex)
            {
                tCPClientResult.Result = false;
                tCPClientResult.ErrMSG = ex.Message.ToString();
                Stopwatch.Stop();
                connected = false;
                return tCPClientResult;
            }
            finally
            {
                
            }
        }
        public bool CheckComState()
        {
            //https://learn.microsoft.com/zh-CN/dotnet/api/system.net.sockets.socket.poll?view=netframework-4.0
            try
            {
                byte[] bs = new byte[1];      
                //对于 SelectRead，如果已调用且连接处于挂起状态、数据可供读取，或者连接已关闭、重置或终止，则返回 true
                if (tcpClient.Poll(10, SelectMode.SelectRead)&& tcpClient.Available == 0)
                {
                    connected = false;//服务器踢下线的场景
                }
                else
                {
                    connected = true;
                }
                //对于 SelectError，如果处理Connect未阻止且连接失败的 ，或者如果未OutOfBandInline设置 且带外数据可用，则返回 true 。
                if (tcpClient.Poll(10, SelectMode.SelectError))//服务器没开的场景
                {
                    return false;
                }
                return connected;
            }
            catch (Exception ex)
            {
                connected = false;
                return connected;
            }
            
        }
        public bool CheckObj(byte[] arg1,byte[]arg2)
        {
            if (arg1 == null || arg2 == null) return false;
            if (arg1.Length != arg2.Length) return false;
            for (int i = 0; i < arg1.Length; i++)
            {
                if (arg1[i] != arg2[i])
                {
                    return false;
                }
            }
            return true;
        }
        public class TCPClientResult
        {
            public byte[] SendBytes { get; set; }
            public byte[] ReciveBytes { get; set; }
            public string SendMsg { get; set; }
            public string ReciveMsg { get; set; }
            public bool Result { get; set; }
            public string ErrMSG { get; set; }
            public uint Time { get; set; }
        }
    }
}
